
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.Timer;
import javax.swing.UIManager;

// Tron looking Kalah GUI.

public class TronKalahGUI extends JFrame {
	public static final int INTERACTIVE_PLAYER = 0;
	public static int AI_PLAYER = 1 - INTERACTIVE_PLAYER;
	// Implement a canvas, where media is drawn and events are processed
	public static class Canvas extends JComponent implements MouseListener {
		// Describes the positon of a cup on the board
		public static class Cup {
			public int row;
			public int column = -2;
			
			// Returns the X coordinate of the center of a specified cup
			public int get_x( ) {
				return (int)( column*85.5 ) + 197;
			}

			// Returns the Y coordinate of the center of a specified cup
			public int get_y( ) {
				return ( (1-row) * 100 ) + 290;
			}
		}
		
		BufferedImage background;
		Font font;
		group1_Player enemy;
		interactive_Player user;
		KalahPieGameState  game;
		KalahPieGameState  game_copy;
		
		int winner;
		boolean game_has_ended;
		String console_text;
		
		// chosen_cup is the cup that the player last chose
		Cup chosen_cup = new Cup();
		// Variables to determine if a move was requested by a player, or an animation is in progress
		boolean move_requested = false;
		boolean animating = false;
		
		// The animation timer controls the speed of the seed-dropping animation.
		LogicTimer animation_timer = new LogicTimer( 0.3 );
		// The blink timer controls the speed of the blinking curosor
		LogicTimer blink_timer     = new LogicTimer( 2.0 );
		// The ai move timer controls how much of a delay there is before performing the ai's move
		LogicTimer ai_move_timer     = new LogicTimer( 2.0 );
		
		public Canvas( ) {
			super();
			start_new_game();
			this.font  = new Font("TR2N", Font.PLAIN, 32 );
			
			try {
				background = ImageIO.read( new File("Media/TronBackground.png" ) );
			} catch (IOException ex) {
				Logger.getLogger(TronKalahGUI.class.getName()).log(Level.SEVERE, null, ex);
			}
		}
		@Override
		public void paint( Graphics g ) {
			Graphics2D g2d = (Graphics2D)g;
			g2d.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
			// Draw the background image
			g.drawImage(background, 0, 0, null);
			g.setFont(font);
			g.setColor( Color.CYAN );
			
			draw_centered_string(g, console_text, 400, 520 );
			draw_current_player_cursor(g);
			draw_animated_move( g );
			draw_ai_selected_move(g);
			
			draw_cups(g);
			super.paint(g);
		}


		@Override
		public void mouseClicked(MouseEvent me) {
			int x = me.getX();
			int y = me.getY();
			//System.out.println( x + ", " + y );
			int move = get_move_under_position(x, y);
			if( game.checkEndGame() ) {
				start_new_game();
			} else {
				// Allow the player to choose a move if it is their turn
				if( game.CurrentPlayer() == INTERACTIVE_PLAYER && !move_requested && !animating ) {
					if( game.validMove(move) ) {
						try {
							request_move(move);
							console_text = "";
							this.repaint();
						} catch (Exception ex) {
							Logger.getLogger(TronKalahGUI.class.getName()).log(Level.SEVERE, null, ex);
						}
					}
				}
			}
		}
		
		// OnUpdate: Called in a timer periodically, use to update the logic of the program.
		public void OnUpdate( double dt ) {
			// If we are animating a move, logic goes here
			if( animating ) {
				animation_timer.tick( dt );
				// The animation timer is the delay that the animation has before updating a new cup
				if( animation_timer.has_expired() ) {
					int y = chosen_cup.row;
					int x = chosen_cup.column;
					
					// If the move was a pie move then just start at zero
					if( x < 0 )
						x = 0;
					
					// Copy the cup from the new game state into the current one
					game.grid[y][x] = game_copy.grid[y][x];
					// Advance the cup forward.
					if( chosen_cup.row == 0 ) {
						chosen_cup.column++;
						// If we go too far to the right, then change the row and update the score
						if( chosen_cup.column > 5 ) {
							chosen_cup.row = 1;
							chosen_cup.column = 5;
							game.score[0] = game_copy.score[0];
						}
					} else {
						// If we go too far to the left, then change the row and update the score
						chosen_cup.column--;
						if( chosen_cup.column < 0 ) {
							chosen_cup.row = 0;
							chosen_cup.column = 0;
							game.score[1] = game_copy.score[1];
						}
					}
					
					// If the new game state is equal to the old one, then we can stop animating because there's nothing to show
					if( game.grid[chosen_cup.row][chosen_cup.column] == game_copy.grid[chosen_cup.row][chosen_cup.column] ) {
						// Replace the game with the new game state and stop animating
						game = game_copy;
						animating = false;
						chosen_cup.row = y;
						chosen_cup.column = x;
					}
					
					animation_timer.reset();
				}
			} else {
				if( !game.checkEndGame() ) {
					blink_timer.tick( dt );

					// Reset the blink timer so that it continues indefinitely
					if( blink_timer.has_expired() )
						blink_timer.reset();

					// See if a player is trying to make a move
					if( move_requested ) {
						ai_move_timer.tick(dt);
						if( ( game.CurrentPlayer() == AI_PLAYER && ai_move_timer.has_expired()) || game.CurrentPlayer() == INTERACTIVE_PLAYER ) {
							// Create a copy so that we can do the animation
							game_copy = (KalahPieGameState)game.copy();
							try {
								// Play that move on the games copy, later it will be compared to the current game
								game_copy.play( chosen_cup.column );
							} catch (Exception ex) {
								Logger.getLogger(TronKalahGUI.class.getName()).log(Level.SEVERE, null, ex);
							}
							// Signal that we are no longer requesting a move and are in an animating state
							move_requested = false;
							animating = true;
							this.repaint();
						}
						
					} else {
						if( game.CurrentPlayer() == AI_PLAYER && !move_requested ) {
							try {
								int move = enemy.getMove( game );
								while (!game.validMove(move)) {
									move = enemy.getMove( game );
								}
								request_move(move);
								if( move == -1 )
									console_text = "CLU HAS PERFORMED PIE MOVE";
								else
									console_text = "CLU HAS PERFORMED MOVE " + move;
								this.repaint();
							} catch (Exception ex) {
								Logger.getLogger(TronKalahGUI.class.getName()).log(Level.SEVERE, null, ex);
							}
						}
					}
				} else {
					// If the game has ended, perform the one-time operation of seeing who won
					if( game_has_ended == false ) {
						game_has_ended = true;
						game.computeFinalScore();
						// Detect the winner
						if(        game.getScore( 0 ) > game.getScore( 1 ) ) {
							winner = 0;
						} else if( game.getScore( 0 ) < game.getScore( 1 ) ) {
							winner = 1;
						} else {
							// It was a tie!
							winner = -1;
						}
						if( winner == AI_PLAYER ) {
							console_text = "CLU WINS. END OF LINE";
						} else if( winner == INTERACTIVE_PLAYER ) {
							console_text = "YOU WIN.";
						} else {
							console_text = "A TIE. YOU ARE MATCHED";
						}
					}
				}
			}
			this.repaint();
		}

		// Schedules a move to be performed by the current player, and resets the animation timer and ai move timer
		private void request_move( int move ) {
			chosen_cup.column = move;
			chosen_cup.row = game.CurrentPlayer();
			animation_timer.reset();
			if( game.CurrentPlayer() == AI_PLAYER )
				ai_move_timer.reset();
			move_requested = true;
		}
		
		// Returns the move that is under the x and y position, or -2 if this is no move (-1 is the pie move)
		private int get_move_under_position( int x, int y ) {
			// These values were found by doing some math on the background image
			int move = -2;
			// If the interactive player is player 1, then their row is the top row.
			if( INTERACTIVE_PLAYER == 1 ) {
				if( y > 277 && y < 329 ) {
					// Then interpolate across the span of cups, with 0 on the left and 5 on the right
					move = x - 187;
					move = (move*6)/( 654 - 187 );
				} else if( y > 380 && y < 429 ) {
					// If they clicked the other persons row, then that's the pie move.
					return -1;
				}
			} else {
				// If the mouse's y position is within the range of the players pieces
				if( y > 380 && y < 429 ) {
					// Then interpolate across the span of cups, with 0 on the left and 5 on the right
					move = x - 187;
					move = (move*6)/( 654 - 187 );
				} else if( y > 277 && y < 329 ) {
					// If they clicked the other persons row, then that's the pie move.
					return -1;
				}
			}
			return move;
		}
		
		// Draws the cups, showing how many pieces each one has
		private void draw_cups( Graphics g ) {
			// Draw all of the cups
			g.setColor( Color.YELLOW );
			for( int row = 0; row < 2; row++ ) {
				for( int col = 0; col < 6; col++ ) {
					int pieces = game.stonesAt( row, col );
					draw_centered_string( g, "" + pieces, (int)( col*85.5 ) + 197, ( (1-row) * 100 ) + 290);
				}
			}
			// Draw the scores
			g.setColor( Color.CYAN );
			draw_centered_string( g, "" + game.getScore( 1 ), 105, 340 );
			draw_centered_string( g, "" + game.getScore( 0 ), 800-95, 340 );
		}
		
		
		// Draw a blinking cursor at the row of cups for the current player
		private void draw_current_player_cursor( Graphics g ) {
			if( !game_has_ended && blink_timer.get_completion_status() > 0.5 ) {
				if( game.CurrentPlayer() == 1 ) {
					g.setColor(Color.MAGENTA);
					g.fillRect( 145, 248, 20, 56 );
				} else {
					g.fillRect( 145, 352, 20, 56 );
				}
			}
		}
		
		// Draws a string that is centered at a certain location
		private void draw_centered_string( Graphics graphics, String string, int x, int y) {
			int half_width = graphics.getFontMetrics().stringWidth(string)/2;
			graphics.drawString( string, x-half_width, y );
		}
		
		// Draws an icon to represent the move that was animated
		private void draw_animated_move(Graphics g) {
			if( !game_has_ended && chosen_cup.column != -2 && animating ) {
				int x = chosen_cup.get_x();
				int y = chosen_cup.get_y();

				g.setColor( Color.YELLOW );
				g.fillRect( x-15, y+5, 30, 5 );
			}
		}
		
		// If a move is requested and its the enemy player's turn, then draw a little box around their selected move.
		private void draw_ai_selected_move(Graphics g) {
			if( move_requested && game.CurrentPlayer() == AI_PLAYER ) {
				int width = 70;
				int height = 80;
				int x = chosen_cup.get_x() - width/2;
				int y = chosen_cup.get_y() - height/2 - 10;
				g.setColor( Color.CYAN );
				g.drawRect( x, y, width, height);
				g.drawRect( x+1, y+1, width-2, height-2);
				g.drawRect( x+2, y+2, width-4, height-4);
			}
		}
		
		// Creates a new game.
		private void start_new_game() {
			this.console_text = "WELCOME USER.";
			this.winner = -1;
			this.game_has_ended = false;
			// Create the game and its players
			this.user  = new interactive_Player( INTERACTIVE_PLAYER );
			this.game  = new KalahPieGameState( 4 );
			this.enemy = new group1_Player( AI_PLAYER );
		}

		@Override
		public void mousePressed(MouseEvent me) {}

		@Override
		public void mouseReleased(MouseEvent me) {}

		@Override
		public void mouseEntered(MouseEvent me) {}

		@Override
		public void mouseExited(MouseEvent me) {}
	}
	
	// Implement an ActionListener so that we can update Canvas at regular intervals
	public static class OnUpdateListener implements ActionListener {
		Canvas canvas;
		
		public OnUpdateListener( Canvas canvas ) {
			this.canvas = canvas;
		}
		
		// Call OnUpdate when we receive an event for executing the timer
		@Override
		public void actionPerformed(ActionEvent ae) {
			canvas.OnUpdate( 0.1 );
		}
	}
	
	// Implement a simple logical timer for the game
	public static class LogicTimer {
		private double max_time;
		private double current_time;
		
		public LogicTimer( double max_time ) {
			this.max_time = max_time;
			this.current_time = max_time;
		}
		
		// Resets the timer
		public void reset() {
			this.current_time = this.max_time;
		}
		
		// Resets the timer with a new max_time
		public void reset( double max_time ) {
			this.max_time = max_time;
			this.current_time = max_time;
		}
		
		// Updates the current time, given the amount of time that has elapsed since a previous call
		public void tick( double dt ) {
			this.current_time -= dt;
		}
		
		// Return the current time
		public double time( ) {
			return current_time;
		}
		
		// Returns true if this timer has expired (the amount of time that has passed is more than the max time)
		public boolean has_expired() {
			return current_time <= 0.0;
		}
		
		// Returns a number between 0.0 and 1.0 that indicates the completion status of the timer
		public double get_completion_status() {
			return ( max_time - current_time )/max_time;
		}
	}
	
	public static void main(String[] args) {
		new TronKalahGUI();
	}
	
	public TronKalahGUI() {
		super( "TronKalah" );
		
		// Set the native look-and-feel
		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch(Exception e) {
			System.out.println( "Error setting native LAF: " + e );
		}
		// Set the default close operation to exit the program (when you click the X button on the window)
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		// Set the default size and position of the window
		setBounds(30, 30, 800, 600);
		// Create a new canvas
		Canvas canvas = new Canvas( );
		// Set the canvas to be a mouse listener, to receive click events
		addMouseListener(canvas);
		// Add the canvas to the frame
		getContentPane().add(canvas);
		// Create a timer to update Canvas every 0.5 seconds
		Timer timer = new Timer( 50, new OnUpdateListener( canvas ) );
		timer.setInitialDelay(400);
		timer.start();
		// Make the frame visible
		setVisible( true );
	}
}
